package com.quectel.business.common.schedulingapply;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.ArrayUtil;
import com.google.common.base.Preconditions;
import com.quectel.business.common.dept.DeptPersonMsgBusiness;
import com.quectel.business.common.scheduling.SchedulingLeaveBusiness;
import com.quectel.business.common.util.sys.SysCodeUtils;
import com.quectel.constant.PushConstants;
import com.quectel.constant.core.dept.DeptPersonMsgConstants;
import com.quectel.constant.core.scheduling.SchedulingApplyConstants;
import com.quectel.constant.core.scheduling.SchedulingAttendanceDailyConstants;
import com.quectel.constant.core.scheduling.SchedulingLeaveConstants;
import com.quectel.constant.global.SystemConstants;
import com.quectel.constant.sys.SysCodeKeyConstants;
import com.quectel.core.module.dept.dto.DeptPersonDto;
import com.quectel.core.module.dept.service.DeptPersonService;
import com.quectel.core.module.scheduling.dto.*;
import com.quectel.core.module.scheduling.service.*;
import com.quectel.core.module.schedulingapply.dto.*;
import com.quectel.core.module.schedulingapply.dto.vo.SchedulingTimeVO;
import com.quectel.core.module.schedulingapply.service.*;
import com.quectel.util.common.DateUtils;
import com.quectel.util.common.JacksonUtils;
import com.quectel.util.common.Response;
import com.quectel.util.thread.ThreadPoolUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author zhangheng
 * @email nolan.zhang@quectel.com
 * @date 2023/7/10 14:29
 * @description 假期相关
 */
@Component
@Slf4j
public class SchedulingApplyBusiness {


    @DubboReference
    private SchedulingApplyRecordService schedulingApplyRecordService;
    @DubboReference
    private SchedulingLeaveConfigService schedulingLeaveConfigService;
    @DubboReference
    private DeptPersonService deptPersonService;
    @DubboReference
    private SchedulingAuditPipelineService schedulingAuditPipelineService;
    @DubboReference
    private SchedulingApplyRecordAuditLogService schedulingApplyRecordAuditLogService;
    @DubboReference
    private SchedulingApplyRecordAuditPersonService schedulingApplyRecordAuditPersonService;
    @DubboReference
    private SchedulingAttendanceGroupPersonBindingService schedulingAttendanceGroupPersonBindingService;
    @DubboReference
    private SchedulingAttendanceGroupService schedulingAttendanceGroupService;
    @DubboReference
    private SchedulingWorkOvertimeConfigService schedulingWorkOvertimeConfigService;

    @DubboReference
    private SchedulingApplyLeaveRecordService schedulingApplyLeaveRecordService;
    @DubboReference
    private SchedulingApplyResumptionLeaveRecordService schedulingApplyResumptionLeaveRecordService;
    @DubboReference
    private SchedulingApplyRenewCardRecordService schedulingApplyRenewCardRecordService;
    @DubboReference
    private SchedulingApplyOvertimeRecordService schedulingApplyOvertimeRecordService;
    @DubboReference
    private SchedulingApplyOutRecordService schedulingApplyOutRecordService;
    @DubboReference
    private SchedulingApplyTripRecordService schedulingApplyTripRecordService;
    @DubboReference
    private SchedulingApplyTransferClassRecordService schedulingApplyTransferClassRecordService;
    @DubboReference
    private SchedulingAttendanceDailyTimeService schedulingAttendanceDailyTimeService;
    @DubboReference
    private SchedulingAttendanceDailyService schedulingAttendanceDailyService;

    private static final ThreadPoolExecutor SCHEDULING_APPLY_RECORD_THREAD_POOL = ThreadPoolUtils.createCommonIOBoundPool("scheduling_apply_record_thread_pool_");


    @Autowired
    private SchedulingLeaveBusiness schedulingLeaveBusiness;
    @Autowired
    private DeptPersonMsgBusiness deptPersonMsgBusiness;

    /**
     * 推送消息
     *
     * @param schedulingApplyRecordDto 审批记录
     * @param status                   审批状态
     * @param nextSeq                  下个节点的序号
     */
    public void pushMsg(SchedulingApplyRecordDto schedulingApplyRecordDto, Byte status, Integer nextSeq) {
        //发送消息
        List<DeptPersonDto> deptPersonDtos = new ArrayList<>();
        String title = "";
        String desc = "";
        String applyTypeName = SysCodeUtils.easyToGetName(SysCodeKeyConstants.SCHEDULING_AUDIT_PIPELINE_TYPE, schedulingApplyRecordDto.getApplyType().toString());
        if (!SchedulingApplyConstants.CURRENT_AT_SEQ_OF_CLOSE.equals(nextSeq)) {
            // 审核中，通知下一批审核人
            title = "【审批流程】您有新的审批流程待审批!";
            List<SchedulingApplyRecordAuditPersonDto> schedulingApplyRecordAuditPersonDto = schedulingApplyRecordAuditPersonService.selectByRecordAndSeq(schedulingApplyRecordDto.getId(), nextSeq);
            if (CollectionUtils.isNotEmpty(schedulingApplyRecordAuditPersonDto)) {
                schedulingApplyRecordAuditPersonDto.forEach(auditPersonDto -> {
                    DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(auditPersonDto.getAuditDeptPersonId());
                    if (deptPersonDto != null) {
                        deptPersonDtos.add(deptPersonDto);
                    }
                });
            }
            desc = schedulingApplyRecordDto.getCreator() + "发起了一条" + applyTypeName + "流程，请您审批";
        } else {
            if (SchedulingApplyConstants.APPROVED_STATUS.equals(status)) {
                //同意
                title = "【审批流程】您提交的审批" + applyTypeName + "已通过!";
                desc = "您的申请已被通过，请查看";
            } else {
                //不同意
                title = "【审批流程】您提交的审批" + applyTypeName + "被驳回!";
                desc = "您的申请已被驳回，请查看";
            }
        }
        if (CollectionUtils.isNotEmpty(deptPersonDtos)) {

            deptPersonMsgBusiness.pushMsg(deptPersonDtos
                    , DeptPersonMsgConstants.SourceType.SCHEDULING_APPLY_MSG
                    , schedulingApplyRecordDto.getId()
                    , title, desc, true
                    , new PushConstants.PushType[]{PushConstants.PushType.WECHAT_OFFICIAL});
        }
    }


    /**
     * 查询人员适配的加班配置
     *
     * @param personId
     * @return
     */
    public Response<SchedulingWorkOvertimeConfigDto> getOvertimeConfigByPerson(Long personId) {
        SchedulingAttendanceGroupDto schedulingAttendanceGroupDto = schedulingAttendanceGroupPersonBindingService.selectGroupByPersonId(personId);
        if (schedulingAttendanceGroupDto == null) {
            return Response.error("该人员没有归属的考勤组");
        }
        SchedulingWorkOvertimeConfigDto schedulingWorkOvertimeConfigDto = schedulingWorkOvertimeConfigService.selectById(schedulingAttendanceGroupDto.getWorkOvertimeId());
        if (schedulingWorkOvertimeConfigDto == null) {
            return Response.error("该人员考勤组没有设置加班规则");
        }
        return Response.<SchedulingWorkOvertimeConfigDto>ok().wrap(schedulingWorkOvertimeConfigDto);
    }

    /**
     * 根据条件查询申请记录
     *
     * @param tenantId  租户id
     * @param villageId 小区id
     * @param personId  人员id
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @param statuses  原始申请记录的状态
     * @return 原始记录
     */
    public List<SchedulingApplyRecordDto> getApplyRecordByTypeAndTime(Long tenantId, Long villageId, Long personId, String startTime, String endTime, Byte[] applyTypes, Byte[] statuses) {

        CompletableFuture<List<SchedulingApplyOvertimeRecordDto>> overTimeFuture = null;
        if (ArrayUtil.contains(applyTypes, SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType())) {
            overTimeFuture = CompletableFuture.supplyAsync(
                    () -> {
                        Map<String, Object> params = new HashMap<>();
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getTenantId), tenantId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getVillageId), villageId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getApplyDeptPersonId), personId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getStartTime), startTime);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getEndTime), endTime);
                        params.put(SystemConstants.STATUSES, statuses);
                        return schedulingApplyOvertimeRecordService.queryRecordList(params);
                    },
                    SCHEDULING_APPLY_RECORD_THREAD_POOL);
        }

        CompletableFuture<List<SchedulingApplyLeaveRecordDto>> leaveFuture = null;
        if (ArrayUtil.contains(applyTypes, SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType())) {
            //请假
            leaveFuture = CompletableFuture.supplyAsync(
                    () -> {
                        Map<String, Object> params = new HashMap<>();
                        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getTenantId), tenantId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getVillageId), villageId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getApplyDeptPersonId), personId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getStartTime), startTime);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getEndTime), endTime);
                        params.put(SystemConstants.STATUSES, statuses);
                        return schedulingApplyLeaveRecordService.queryRecordList(params);
                    },
                    SCHEDULING_APPLY_RECORD_THREAD_POOL);
        }

        CompletableFuture<List<SchedulingApplyOutRecordDto>> outFuture = null;
        if (ArrayUtil.contains(applyTypes, SchedulingApplyConstants.ApplyTypeEnum.OUT.getType())) {
            //外出
            outFuture = CompletableFuture.supplyAsync(
                    () -> {
                        Map<String, Object> params = new HashMap<>();
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getTenantId), tenantId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getVillageId), villageId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getApplyDeptPersonId), personId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getStartTime), startTime);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getEndTime), endTime);
                        params.put(SystemConstants.STATUSES, statuses);
                        return schedulingApplyOutRecordService.queryRecordList(params);
                    },
                    SCHEDULING_APPLY_RECORD_THREAD_POOL);
        }

        CompletableFuture<List<SchedulingApplyTripRecordDto>> tripFuture = null;
        if (ArrayUtil.contains(applyTypes, SchedulingApplyConstants.ApplyTypeEnum.OUT.getType())) {
            //出差
            tripFuture = CompletableFuture.supplyAsync(
                    () -> {
                        Map<String, Object> params = new HashMap<>();
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getTenantId), tenantId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getVillageId), villageId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getApplyDeptPersonId), personId);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getStartTime), startTime);
                        params.put(LambdaUtil.getFieldName(SchedulingApplyOvertimeRecordDto::getEndTime), endTime);
                        params.put(SystemConstants.STATUSES, statuses);
                        return schedulingApplyTripRecordService.queryRecordList(params);
                    },
                    SCHEDULING_APPLY_RECORD_THREAD_POOL);
        }

        if (overTimeFuture != null) {
            overTimeFuture.join();
        }
        if (leaveFuture != null) {
            leaveFuture.join();
        }
        if (outFuture != null) {
            outFuture.join();
        }
        if (tripFuture != null) {
            tripFuture.join();
        }

        List<SchedulingApplyRecordDto> result = new ArrayList<>();
        //查询对应的record
        try {
            if (overTimeFuture != null && CollectionUtils.isNotEmpty(overTimeFuture.get())) {
                List<SchedulingApplyRecordDto> collect = overTimeFuture.get().stream().map(dto -> schedulingApplyRecordService.selectById(dto.getApplyRecordId())).filter(Objects::nonNull).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(collect)) {
                    result.addAll(collect);
                }
            }
            if (leaveFuture != null && CollectionUtils.isNotEmpty(leaveFuture.get())) {
                List<SchedulingApplyRecordDto> collect = leaveFuture.get().stream().map(dto -> schedulingApplyRecordService.selectById(dto.getApplyRecordId())).filter(Objects::nonNull).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(collect)) {
                    result.addAll(collect);
                }
            }
            if (outFuture != null && CollectionUtils.isNotEmpty(outFuture.get())) {
                List<SchedulingApplyRecordDto> collect = outFuture.get().stream().map(dto -> schedulingApplyRecordService.selectById(dto.getApplyRecordId())).filter(Objects::nonNull).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(collect)) {
                    result.addAll(collect);
                }
            }
            if (tripFuture != null && CollectionUtils.isNotEmpty(tripFuture.get())) {
                List<SchedulingApplyRecordDto> collect = tripFuture.get().stream().map(dto -> schedulingApplyRecordService.selectById(dto.getApplyRecordId())).filter(Objects::nonNull).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(collect)) {
                    result.addAll(collect);
                }
            }

        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            return Collections.emptyList();
        }

        if (CollectionUtils.isNotEmpty(result)) {
            return result.stream().filter(distinctByKey(SchedulingApplyRecordDto::getId)).collect(Collectors.toList());
        }

        return result;

    }

    public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    /**
     * 校验草稿
     *
     * @param dto      草稿对象
     * @param personId 人员id
     * @return
     */
    public Response checkDraft(SchedulingApplyRecordDto dto, Long personId) {
        if (dto == null) {
            return Response.error("申请记录不存在");
        }
        //如果不是草稿状态或者不是本人操作,都不可以修改
        if (!SchedulingApplyConstants.NOT_SUBMITTED_STATUS.equals(dto.getStatus())) {
            return Response.error("申请记录不可修改");
        }
        if (!personId.equals(dto.getApplyDeptPersonId())) {
            return Response.error("没有修改权限");
        }
        return Response.ok();
    }


    /**
     * 获取申请时长(除请假)
     *
     * @param applyType 申请类型
     * @param startTime 申请开始时间
     * @param endTime   申请结束时间
     * @return SchedulingTimeVO时间对象
     */

    public SchedulingTimeVO getTimeLength(Byte applyType, String startTime, String endTime) {
        Date startDateTime = DateUtils.parse(startTime, DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
        Date endDateDateTime = DateUtils.parse(endTime, DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
        long between = DateUtil.between(startDateTime, endDateDateTime, DateUnit.HOUR);
        SchedulingTimeVO schedulingTimeVO = new SchedulingTimeVO();
        schedulingTimeVO.setTimeUnit(SchedulingLeaveConstants.TimeUnitEnum.HOUR.getUnit());
        schedulingTimeVO.setTimeLength(BigDecimal.valueOf(between));
        return schedulingTimeVO;

    }

    /**
     * 获取请假时长
     *
     * @param leaveId   请假id
     * @param startTime 请假开始时间
     * @param endTime   请假结束时间
     * @return SchedulingTimeVO时间对象
     */
    public SchedulingTimeVO getTimeLength(Long leaveId, String startTime, String endTime) {
        SchedulingTimeVO schedulingTimeVO = new SchedulingTimeVO();
        SchedulingLeaveConfigDto schedulingLeaveConfigDto = schedulingLeaveConfigService.selectById(leaveId);
        if (schedulingLeaveConfigDto == null) {
            return schedulingTimeVO;
        }

        Date startDateTime = DateUtils.parse(startTime, DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
        Date endDateDateTime = DateUtils.parse(endTime, DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
        if (schedulingLeaveConfigDto.getMinUnit().equals(SchedulingLeaveConstants.TimeUnitEnum.HOUR.getUnit())) {
            //如果是小时
            BigDecimal divide = BigDecimal.valueOf(endDateDateTime.getTime()).subtract(BigDecimal.valueOf(startDateTime.getTime())).divide(BigDecimal.valueOf(3600 * 1000), SchedulingLeaveConstants.DEFAULT_SCALE, BigDecimal.ROUND_HALF_UP);
            schedulingTimeVO.setTimeLength(divide);
            schedulingTimeVO.setTimeUnit(SchedulingLeaveConstants.TimeUnitEnum.HOUR.getUnit());

        } else if (schedulingLeaveConfigDto.getMinUnit().equals(SchedulingLeaveConstants.TimeUnitEnum.DAY.getUnit())) {
            //如果是天 则看是多少天
            BigDecimal divide = BigDecimal.valueOf(endDateDateTime.getTime()).subtract(BigDecimal.valueOf(startDateTime.getTime())).divide(BigDecimal.valueOf(24 * 3600 * 1000), SchedulingLeaveConstants.DEFAULT_SCALE, BigDecimal.ROUND_HALF_UP);
            schedulingTimeVO.setTimeUnit(SchedulingLeaveConstants.TimeUnitEnum.DAY.getUnit());
            schedulingTimeVO.setTimeLength(divide);
        }

        return schedulingTimeVO;

    }


    /**
     * 组装审批日志
     *
     * @param schedulingApplyRecordDto 申请记录
     * @return 审批日志列表
     */

    public List<SchedulingApplyRecordAuditLogDto> getAuditLogs(SchedulingApplyRecordDto schedulingApplyRecordDto) {
        List<SchedulingApplyRecordAuditLogDto> auditLogs = schedulingApplyRecordAuditLogService.selectByRecordId(schedulingApplyRecordDto.getId());

        List<SchedulingApplyRecordAuditLogDto> newAuditLogs = new ArrayList<>(auditLogs);
        for (SchedulingApplyRecordAuditLogDto auditLog : newAuditLogs) {
            auditLog.setStageTitle("审批人");
            auditLog.setStatusDesc(auditLog.getAuditDeptPersonName());
        }

        if (SchedulingApplyConstants.INIT_STATUS.equals(schedulingApplyRecordDto.getStatus())) {
            int auditLogSeq = -1;
            if (CollectionUtils.isNotEmpty(newAuditLogs)) {
                auditLogSeq = newAuditLogs.stream().max(Comparator.comparing(SchedulingApplyRecordAuditLogDto::getCurrentAtSeq)).orElse(new SchedulingApplyRecordAuditLogDto()).getCurrentAtSeq();
            }
            final int logSeqMax = auditLogSeq;
            Map<String, Object> auditPersonParams = new HashMap<>();
            auditPersonParams.put(LambdaUtil.getFieldName(SchedulingApplyRecordAuditPersonDto::getApplyRecordId), schedulingApplyRecordDto.getId());
            List<SchedulingApplyRecordAuditPersonDto> auditPersonDtoList = schedulingApplyRecordAuditPersonService.queryList(auditPersonParams);
            List<SchedulingApplyRecordAuditPersonDto> nextAuditList = auditPersonDtoList.stream().filter(x -> x.getCurrentAtSeq() > logSeqMax).collect(Collectors.toList());
            Map<Integer, List<SchedulingApplyRecordAuditPersonDto>> sortedAuditList = nextAuditList.stream().sorted(Comparator.comparing(SchedulingApplyRecordAuditPersonDto::getCurrentAtSeq)).collect(Collectors.groupingBy(SchedulingApplyRecordAuditPersonDto::getCurrentAtSeq));

            // 总状态为审核中的单，日志插入'审核中'节点
            sortedAuditList.forEach((k, v) -> {
                //省略多余的人 只显示2个人姓名
                StringBuilder stringBuilder = new StringBuilder();
                for (int i = 0; i < 3 && i < v.size(); i++) {
                    stringBuilder.append(v.get(i).getDeptPersonName());
                    stringBuilder.append(",");
                }
                String s = StringUtils.removeEnd(stringBuilder.toString(), ",");
                String personNames = v.size() == 1 ? s : s + "等";
                SchedulingApplyRecordAuditLogDto auditNode = new SchedulingApplyRecordAuditLogDto();
                auditNode.setCurrentAtSeq(v.get(0).getCurrentAtSeq());
                auditNode.setApplyRecordId(v.get(0).getApplyRecordId());
                auditNode.setStatus(SchedulingApplyConstants.INIT_STATUS);
                auditNode.setStageTitle("审批人");
                auditNode.setStatusDesc(personNames);
                newAuditLogs.add(auditNode);
            });
        }

        // 日志插入发起节点
        SchedulingApplyRecordAuditLogDto applyNode = new SchedulingApplyRecordAuditLogDto();
        applyNode.setAuditTime(schedulingApplyRecordDto.getCreateTime());
        applyNode.setStatusDesc(schedulingApplyRecordDto.getApplyPersonName() + "发起");
        applyNode.setStageTitle("发起申请");
        newAuditLogs.add(0, applyNode);

        return newAuditLogs;
    }

    /**
     * 申请时获取表单数据
     *
     * @param type         申请类型
     * @param formDataJson 前端传递的调单json数据
     * @return 表单数据对象
     */
    public SchedulingApplyRecordDto.BaseFormData getFormData(Byte type, String formDataJson) {
        SchedulingApplyConstants.ApplyTypeEnum byType = SchedulingApplyConstants.ApplyTypeEnum.getByType(type);
        if (byType == null) {
            return null;
        }
        SchedulingApplyRecordDto.BaseFormData baseFormData = null;

        switch (byType) {
            case APPLY_LEAVE:
                SchedulingApplyRecordDto.LeaveFormData leaveFormData = JacksonUtils.parse(formDataJson, SchedulingApplyRecordDto.LeaveFormData.class);
                SchedulingLeaveConfigDto schedulingLeaveConfigDto = schedulingLeaveConfigService.selectCacheById(leaveFormData.getLeaveId());
                if (schedulingLeaveConfigDto != null) {
                    leaveFormData.setType(schedulingLeaveConfigDto.getType());
                    leaveFormData.setTypeName(SysCodeUtils.easyToGetName(SysCodeKeyConstants.SCHEDULING_LEAVE_TYPE, String.valueOf(leaveFormData.getTypeName())));
                }
                baseFormData = leaveFormData;
                break;
            case RESUMPTION_LEAVE:
                SchedulingApplyRecordDto.ResumptionLeaveFormData resumptionLeaveFormData = JacksonUtils.parse(formDataJson, SchedulingApplyRecordDto.ResumptionLeaveFormData.class);
                SchedulingApplyRecordDto applyRecordDto = schedulingApplyRecordService.selectCacheById(resumptionLeaveFormData.getRecordId());
                if (applyRecordDto != null) {
                    applyRecordDto.setApplyTypeName(SysCodeUtils.easyToGetName(SysCodeKeyConstants.SCHEDULING_AUDIT_PIPELINE_TYPE, String.valueOf(applyRecordDto.getApplyType())));
                    applyRecordDto.setFormData(getFormData(applyRecordDto.getApplyType(), applyRecordDto.getFormDataJson()));
                    resumptionLeaveFormData.setOrderNo(applyRecordDto.getOrderNo());
                    resumptionLeaveFormData.setSchedulingApplyRecord(applyRecordDto);
                }
                baseFormData = resumptionLeaveFormData;
                break;
            case RENEW_CARD:
                baseFormData = JacksonUtils.parse(formDataJson, SchedulingApplyRecordDto.RenewCardFormData.class);
                break;
            case OVER_TIME:
                baseFormData = JacksonUtils.parse(formDataJson, SchedulingApplyRecordDto.OvertimeFormData.class);
                break;
            case OUT:
                baseFormData = JacksonUtils.parse(formDataJson, SchedulingApplyRecordDto.OutFormData.class);
                break;
            case TRIP:
                baseFormData = JacksonUtils.parse(formDataJson, SchedulingApplyRecordDto.TripFormData.class);
                break;
            case TRANSFER_CLASS:
                baseFormData = JacksonUtils.parse(formDataJson, SchedulingApplyRecordDto.TransferClassFormData.class);
                break;
            default:
                baseFormData = JacksonUtils.parse(formDataJson, SchedulingApplyRecordDto.BaseFormData.class);
        }


        return baseFormData;
    }

    /**
     * 包装申请时间
     * 前端不会提交申请时长,由后端根据申请时间计算
     *
     * @param type         申请类型
     * @param baseFormData 申请表单数据
     * @return 包装后的表单数据
     */
    public SchedulingApplyRecordDto.BaseFormData packTimeLength(Byte type, SchedulingApplyRecordDto.BaseFormData baseFormData) {
        SchedulingApplyConstants.ApplyTypeEnum byType = SchedulingApplyConstants.ApplyTypeEnum.getByType(type);
        if (byType == null) {
            return null;
        }

        switch (byType) {
            case APPLY_LEAVE:
                SchedulingApplyRecordDto.LeaveFormData leaveFormData = (SchedulingApplyRecordDto.LeaveFormData) baseFormData;
                Long leaveId = leaveFormData.getLeaveId();
                SchedulingTimeVO leaveTimeLength = getTimeLength(leaveId, leaveFormData.getStartTime(), leaveFormData.getEndTime());
                leaveFormData.setTimeLength(leaveTimeLength.getTimeLength());
                leaveFormData.setTimeUnit(leaveTimeLength.getTimeUnit());
                //查询假期余额
                Response<SchedulingTimeVO> leaveBalance = schedulingLeaveBusiness.getLeaveBalance(leaveFormData.getLeaveId(), leaveFormData.getPersonId(), leaveFormData.getStartTime(), leaveFormData.getEndTime());

                if (leaveBalance.isSuccess()) {
                    leaveFormData.setHistoryBalance(leaveBalance.getData().getTimeLength());
                    leaveFormData.setHistoryBalanceUnit(leaveBalance.getData().getTimeUnit());
                    Response<SchedulingTimeVO> schedulingTimeVOResponse = schedulingLeaveBusiness.calLeaveBalance(leaveBalance.getData(), leaveTimeLength);
                    if(schedulingTimeVOResponse.isSuccess()){
                        leaveFormData.setBalanceDetail(schedulingTimeVOResponse.getData());
                    }
                }

                break;
            case OVER_TIME:
                SchedulingApplyRecordDto.OvertimeFormData overtimeFormData = (SchedulingApplyRecordDto.OvertimeFormData) baseFormData;
                SchedulingTimeVO overtimeLength = getTimeLength(byType.getType(), overtimeFormData.getStartTime(), overtimeFormData.getEndTime());
                overtimeFormData.setTimeLength(overtimeLength.getTimeLength());
                overtimeFormData.setTimeUnit(overtimeLength.getTimeUnit());
                break;
            case OUT:
                SchedulingApplyRecordDto.OutFormData outFormData = (SchedulingApplyRecordDto.OutFormData) baseFormData;
                SchedulingTimeVO outTimeLength = getTimeLength(byType.getType(), outFormData.getStartTime(), outFormData.getEndTime());
                outFormData.setTimeLength(outTimeLength.getTimeLength());
                outFormData.setTimeUnit(outTimeLength.getTimeUnit());
                break;
            case TRIP:
                SchedulingApplyRecordDto.TripFormData tripFormData = (SchedulingApplyRecordDto.TripFormData) baseFormData;
                if (CollectionUtils.isNotEmpty(tripFormData.getTripDataList())) {
                    for (SchedulingApplyRecordDto.TripData tripData : tripFormData.getTripDataList()) {
                        SchedulingTimeVO tripTimeLength = getTimeLength(byType.getType(), tripData.getStartTime(), tripData.getEndTime());
                        tripData.setTimeLength(tripTimeLength.getTimeLength());
                        tripData.setTimeUnit(tripTimeLength.getTimeUnit());
                    }
                }
                break;
            case RESUMPTION_LEAVE:
                //销假没有时间段
            case RENEW_CARD:
                //补卡没有时间段
            case TRANSFER_CLASS:
                //补卡没有时间段
            default:
        }

        return baseFormData;
    }

    /**
     * 申请参数校验
     *
     * @param type         申请类型
     * @param baseFormData 申请表单数据
     * @return 校验结果
     */
    public Response checkApply(Byte type, SchedulingApplyRecordDto.BaseFormData baseFormData) {
        SchedulingApplyConstants.ApplyTypeEnum byType = SchedulingApplyConstants.ApplyTypeEnum.getByType(type);
        if (byType == null) {
            return Response.error("申请类型不合法");
        }
        if (baseFormData.getPersonId() == null || baseFormData.getExtFileFlag() == null) {
            return Response.error("缺少人员ID和附件标识");
        }

        DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(baseFormData.getPersonId());
        if (deptPersonDto == null) {
            return Response.error("人员不存在");
        }
        SchedulingAttendanceGroupDto schedulingAttendanceGroupDto = schedulingAttendanceGroupPersonBindingService.selectGroupByPersonId(baseFormData.getPersonId());
        if (schedulingAttendanceGroupDto == null) {
            return Response.error("该人员没有归属的考勤组");
        }

        Response response = Response.ok();
        switch (byType) {
            case APPLY_LEAVE:
                response = checkLeaveApply(baseFormData);
                break;
            case RESUMPTION_LEAVE:
                response = checkResumptionLeaveApply(baseFormData);
                break;
            case RENEW_CARD:
                response = checkRenewCardApply(baseFormData);
                break;
            case OVER_TIME:
                response = checkOvertimeApply(baseFormData);
                break;
            case OUT:
                response = checkOutApply(baseFormData);
                break;
            case TRIP:
                response = checkTripApply(baseFormData);
                break;
            case TRANSFER_CLASS:
                response = checkTransferClassApply(baseFormData);
                break;
            default:
        }
        return response;
    }

    /**
     * 校验审批流配置
     *
     * @param dto 审批流配置
     * @return 校验结果
     */
    public Response checkPipeline(SchedulingAuditPipelineDto dto) {
        if (dto == null) {
            return Response.error("没有配置审批流程，请联系管理员");
        }
        if (CollectionUtils.isEmpty(dto.getAuditPipelineSeqDtos())) {
            return Response.error("审批流程不完整，请联系管理员");
        }
        if (SystemConstants.ABNORMAL.equals(dto.getStatus())) {
            return Response.error("审批流程不可用，请联系管理员");
        }

        return Response.ok();
    }


    /**
     * 校验请假申请
     *
     * @param baseFormData 请假表单数据
     * @return 校验结果
     */
    private Response checkLeaveApply(SchedulingApplyRecordDto.BaseFormData baseFormData) {

        SchedulingApplyRecordDto.LeaveFormData leaveFormData = (SchedulingApplyRecordDto.LeaveFormData) baseFormData;
        Date startTime = DateUtils.parse(leaveFormData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
        Date endTime = DateUtils.parse(leaveFormData.getEndTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
        DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(leaveFormData.getPersonId());
        Assert.notNull(deptPersonDto);
        //同一个时间段,待审核,已同意的都不可以
        Byte[] statuses = new Byte[]{SchedulingApplyConstants.INIT_STATUS, SchedulingApplyConstants.APPROVED_STATUS};
        List<SchedulingApplyRecordDto> list = getApplyRecordByTypeAndTime(
                deptPersonDto.getTenantId(),
                deptPersonDto.getVillageId(),
                deptPersonDto.getId(),
                leaveFormData.getStartTime(),
                leaveFormData.getEndTime(),
                new Byte[]{SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType(), SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType(), SchedulingApplyConstants.ApplyTypeEnum.OUT.getType(), SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType()},
                statuses);
        if (CollectionUtils.isNotEmpty(list)) {
            return Response.error("同一时间段有其他申请，请修改时间段");
        }

        return schedulingLeaveBusiness.checkLeaveApplication(
                leaveFormData.getLeaveId(),
                leaveFormData.getPersonId(),
                startTime,
                endTime,
                leaveFormData.getReason(),
                leaveFormData.getExtFileFlag()
        );
    }

    /**
     * 校验补卡申请
     *
     * @param baseFormData 补卡表单数据
     * @return 审核结果
     */
    private Response checkRenewCardApply(SchedulingApplyRecordDto.BaseFormData baseFormData) {
        SchedulingApplyRecordDto.RenewCardFormData renewCardFormData = (SchedulingApplyRecordDto.RenewCardFormData) baseFormData;
        if (StringUtils.isBlank(renewCardFormData.getRenewCardTime())) {
            return Response.error("补卡日期必填");
        }
        //无须查询当天是否缺卡,按照申请时间来

        return Response.ok();
    }

    /**
     * 校验销假申请
     *
     * @param baseFormData 销假表单数据
     * @return 审核结果
     */
    private Response checkResumptionLeaveApply(SchedulingApplyRecordDto.BaseFormData baseFormData) {
        SchedulingApplyRecordDto.ResumptionLeaveFormData resumptionLeaveFormData = (SchedulingApplyRecordDto.ResumptionLeaveFormData) baseFormData;
        if (resumptionLeaveFormData.getRecordId() == null) {
            return Response.error("销假记录必填");
        }
        SchedulingApplyRecordDto schedulingApplyRecordDto = schedulingApplyRecordService.selectById(resumptionLeaveFormData.getRecordId());
        if (schedulingApplyRecordDto == null) {
            return Response.error("销假记录不存在");
        }
        //未完成流程不用销假
        if (!SchedulingApplyConstants.APPROVED_STATUS.equals(schedulingApplyRecordDto.getStatus())) {
            return Response.error("该记录未完成审核，请联系审批节点人员");
        }
        //申请人必须是本人
        if (!schedulingApplyRecordDto.getApplyDeptPersonId().equals(baseFormData.getPersonId())) {
            return Response.error("非本人记录无法操作");
        }
        SchedulingApplyRecordDto applyRecordDto = schedulingApplyRecordService.selectById(resumptionLeaveFormData.getRecordId());
        //如果不是已通过 则不可以销假
        if (!SchedulingApplyConstants.APPROVED_STATUS.equals(applyRecordDto.getStatus())) {
            return Response.error("未审核通过的记录不可以申请核销");
        }

        return Response.ok();
    }

    /**
     * 校验加班申请
     *
     * @param baseFormData 加班表单数据
     * @return 校验结果
     */
    private Response checkOvertimeApply(SchedulingApplyRecordDto.BaseFormData baseFormData) {
        SchedulingApplyRecordDto.OvertimeFormData overtimeFormData = (SchedulingApplyRecordDto.OvertimeFormData) baseFormData;

        if (StringUtils.isBlank(overtimeFormData.getStartTime()) || StringUtils.isBlank(overtimeFormData.getEndTime())) {
            return Response.error("开始时间或者结束时间必传");
        }
        //当前申请时间必须大于当前时间
        Date startDate = DateUtils.parse(overtimeFormData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
        Date endDate = DateUtils.parse(overtimeFormData.getEndTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
        Date now = new Date();
        if (now.after(startDate) || now.after(endDate)) {
            return Response.error("只能申请将来时间段的加班");
        }

        DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(overtimeFormData.getPersonId());
        Assert.notNull(deptPersonDto);
        //同一个时间段,待审核,已同意的都不可以
        Byte[] statuses = new Byte[]{SchedulingApplyConstants.INIT_STATUS, SchedulingApplyConstants.APPROVED_STATUS};
        List<SchedulingApplyRecordDto> list = getApplyRecordByTypeAndTime(
                deptPersonDto.getTenantId(),
                deptPersonDto.getVillageId(),
                deptPersonDto.getId(),
                overtimeFormData.getStartTime(),
                overtimeFormData.getEndTime(),
                new Byte[]{SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType(), SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType(), SchedulingApplyConstants.ApplyTypeEnum.OUT.getType(), SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType()},
                statuses);
        if (CollectionUtils.isNotEmpty(list)) {
            return Response.error("同一时间段有其他申请，请修改时间段");
        }
        //加班规则校验
        //查询考勤组 查询加班规则
        Response<SchedulingWorkOvertimeConfigDto> overtimeConfigByPerson = getOvertimeConfigByPerson(deptPersonDto.getId());
        if (!overtimeConfigByPerson.isSuccess()) {
            return Response.error(overtimeConfigByPerson.getMessage());
        }
        SchedulingWorkOvertimeConfigDto schedulingWorkOvertimeConfigDto = overtimeConfigByPerson.getData();


        //判断请假时间在哪一天,对应的是工作日还是休息日还是节假日

        Map<String, Object> params = new HashMap<>();
        params.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyTimeDto::getPersonId), overtimeFormData.getPersonId());
        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getStartTime), overtimeFormData.getStartTime());
        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getEndTime), overtimeFormData.getEndTime());
        List<SchedulingAttendanceDailyTimeDto> dailyTimeDtos = schedulingAttendanceDailyTimeService.queryList(params);
        if (CollectionUtils.isEmpty(dailyTimeDtos)) {
            return Response.error("未查询到排班信息");
        }
        Map<Long, List<SchedulingAttendanceDailyTimeDto>> collect = dailyTimeDtos.parallelStream()
                .collect(Collectors.groupingBy(SchedulingAttendanceDailyTimeDto::getAttendanceDailyId));

        //只允许申请一天的加班
        if (collect.size() > 1) {
            return Response.error("只允许申请一天的加班，不能跨天申请时");
        }

        Long l = collect.keySet().stream().findFirst().orElse(null);
        SchedulingAttendanceDailyDto schedulingAttendanceDailyDto = schedulingAttendanceDailyService.selectById(l);
        //申请时长就是加班时长

        //如果是工作日
        if (SchedulingAttendanceDailyConstants.SituationTypeEnum.WORK.getType().equals(schedulingAttendanceDailyDto.getSituation())) {
            SchedulingWorkOvertimeConfigDto.WorkDayConfig workDayConfig = schedulingWorkOvertimeConfigDto.getWorkDayConfig();
            //工作日加班规则
            if (workDayConfig != null) {
                //是否允许加班
                if (workDayConfig.getStatus().equals(SystemConstants.NO)) {
                    return Response.error("工作日不允许加班");
                }
            }
        } else if (SchedulingAttendanceDailyConstants.SituationTypeEnum.REST.getType().equals(schedulingAttendanceDailyDto.getSituation())) {
            SchedulingWorkOvertimeConfigDto.RestDayConfig restDayConfig = schedulingWorkOvertimeConfigDto.getRestDayConfig();
            //休息日加班规则
            if (restDayConfig != null) {
                //是否允许加班
                if (restDayConfig.getStatus().equals(SystemConstants.NO)) {
                    return Response.error("休息日不允许加班");
                }

            }
        } else if (SchedulingAttendanceDailyConstants.SituationTypeEnum.VACATION.getType().equals(schedulingAttendanceDailyDto.getSituation())) {
            SchedulingWorkOvertimeConfigDto.HolidayDayConfig holidayConfig = schedulingWorkOvertimeConfigDto.getHolidayConfig();
            //节假日加班规则
            if (holidayConfig != null) {
                //是否允许加班
                if (holidayConfig.getStatus().equals(SystemConstants.NO)) {
                    return Response.error("节假日不允许加班");
                }
            }
        }


        SchedulingWorkOvertimeConfigDto.OtherConfig otherConfig = schedulingWorkOvertimeConfigDto.getOtherConfig();
        //更多规则
        if (otherConfig != null) {

            //可提交加班的最晚时间限制
            //总加班时长是否限制 累计加班时长不能大于时长
        }


        return Response.ok();
    }

    /**
     * 外出申请校验
     *
     * @param baseFormData 外出申请表单
     * @return 校验结果
     */
    private Response checkOutApply(SchedulingApplyRecordDto.BaseFormData baseFormData) {
        SchedulingApplyRecordDto.OutFormData outFormData = (SchedulingApplyRecordDto.OutFormData) baseFormData;

        if (StringUtils.isBlank(outFormData.getDestination()) || StringUtils.isBlank(outFormData.getStartTime()) || StringUtils.isBlank(outFormData.getEndTime())) {
            return Response.error("必传参数不能为空");
        }
        DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(outFormData.getPersonId());
        Assert.notNull(deptPersonDto);
        //同一个时间段,待审核,已同意的都不可以
        Byte[] statuses = new Byte[]{SchedulingApplyConstants.INIT_STATUS, SchedulingApplyConstants.APPROVED_STATUS};
        List<SchedulingApplyRecordDto> list = getApplyRecordByTypeAndTime(
                deptPersonDto.getTenantId(),
                deptPersonDto.getVillageId(),
                deptPersonDto.getId(),
                outFormData.getStartTime(),
                outFormData.getEndTime(),
                new Byte[]{SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType(), SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType(), SchedulingApplyConstants.ApplyTypeEnum.OUT.getType(), SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType()},
                statuses);
        if (CollectionUtils.isNotEmpty(list)) {
            return Response.error("同一时间段有其他申请，请修改时间段");
        }
        return Response.ok();
    }

    /**
     * 校验出差申请
     *
     * @param baseFormData 出差表单数据
     * @return 校验结果
     */
    private Response checkTripApply(SchedulingApplyRecordDto.BaseFormData baseFormData) {
        SchedulingApplyRecordDto.TripFormData outFormData = (SchedulingApplyRecordDto.TripFormData) baseFormData;
        if (CollectionUtils.isEmpty(outFormData.getTripDataList())) {
            return Response.error("行程不能为空");
        }
        List<SchedulingApplyRecordDto.TripData> tripDataList = outFormData.getTripDataList();
        for (SchedulingApplyRecordDto.TripData tripData : tripDataList) {
            if (StringUtils.isBlank(tripData.getStartTime())
                    || StringUtils.isBlank(tripData.getEndTime())
                    || StringUtils.isBlank(tripData.getStartCityCode())
                    || StringUtils.isBlank(tripData.getEndCityCode())
                    || tripData.getTransportation() == null
                    || tripData.getTripType() == null
            ) {
                return Response.error("必传参数不能为空");
            }
            DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(outFormData.getPersonId());
            Assert.notNull(deptPersonDto);
            //同一个时间段,待审核,已同意的都不可以
            Byte[] statuses = new Byte[]{SchedulingApplyConstants.INIT_STATUS, SchedulingApplyConstants.APPROVED_STATUS};
            List<SchedulingApplyRecordDto> list = getApplyRecordByTypeAndTime(
                    deptPersonDto.getTenantId(),
                    deptPersonDto.getVillageId(),
                    deptPersonDto.getId(),
                    tripData.getStartTime(),
                    tripData.getEndTime(),
                    new Byte[]{SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType(), SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType(), SchedulingApplyConstants.ApplyTypeEnum.OUT.getType(), SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType()},
                    statuses);
            if (CollectionUtils.isNotEmpty(list)) {
                return Response.error("同一时间段有其他申请，请修改时间段");
            }
        }
        if (!checkTimeIntervals(outFormData.getTripDataList())) {
            return Response.error("行程时间有交叉");
        }

        return Response.ok();
    }

    /**
     * 校验出差时间是否有交叉
     *
     * @param tripDataList 出差行程列表
     * @return
     */
    public static boolean checkTimeIntervals(List<SchedulingApplyRecordDto.TripData> tripDataList) {
        // 按照startTime进行排序
        tripDataList.sort(Comparator.comparing(interval -> DateUtils.parse(interval.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT)));
        // 初始化prevEndTime为1970年1月1日的最早时间
        Date prevEndTime = new Date(0);

        for (SchedulingApplyRecordDto.TripData tripData : tripDataList) {
            Date startTime = DateUtils.parse(tripData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
            Date endTime = DateUtils.parse(tripData.getEndTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT);
            Preconditions.checkNotNull(startTime);
            // 存在交叉
            if (startTime.before(prevEndTime)) {
                return false;
            }
            prevEndTime = endTime;
        }
        // 遍历结束后没有交叉
        return true;
    }

    /**
     * 校验换班申请数据
     *
     * @param baseFormData 换班表单申请数据
     * @return 校验结果
     */
    private Response checkTransferClassApply(SchedulingApplyRecordDto.BaseFormData baseFormData) {
        //换班暂时未实现
        return Response.ok();
    }


}
